from google.appengine.api import memcache
from django.http import HttpResponse, HttpResponseRedirect, QueryDict
import simplejson
from django.template.loader import render_to_string
from django.contrib.contenttypes.models import ContentType
from google.appengine.ext import deferred
from oa_cache.models import ListCache, ModelCache, UserSaltCache, interpret_hash, SideEffectCache
from pirate_forum.models import create_view, get_rangelist
from django.template import RequestContext
from pirate_topics.models import Topic


def get_object_or_none(ctype_id, obj_id):
    """
Returns object with ID and ContentType ID
"""
    if ctype_id is not None and obj_id is not None:
        content_type = ContentType.objects.get(pk=ctype_id)
        obj = content_type.get_object_for_this_type(pk=obj_id)
    else:
        obj = None
    return obj


def side_effect(request):
    """
Initiates AJAX side effects, for instance if a user
creates a new object, it renders that new object into the
page and deferrs a pre-rendering task for that list
and object.

This allows the user to see the immediate effect of the
action while ensuring low-latency. See the oa_cache.models
for more information on SideEffectCache
"""
    if request.method == "GET":
        rendered_list = []
        data = {}
        l = None
        usc_pk = request.GET.get('usc_pk')
        se = request.GET.get('side_effect')
        parent_pk = request.GET.get('obj_pk')
        key = request.GET.get('key')[1:]
        jsonval = simplejson.loads(se)
        if jsonval != None:
            obj_id, ctype_id = simplejson.loads(se)
            obj = get_object_or_none(int(ctype_id), int(obj_id))
            usc = UserSaltCache.objects.get(pk=int(usc_pk))
            #First, check to see if we need to switch context
            #EDGE-CASE: if user is viewing comments and submits argument
            if usc.context is not None:
                if key is not None:
                    rendertype, paramdict = interpret_hash(key)
                scroll = paramdict.get('DIM_KEY', None)
                try:
                    l = ListCache.objects.get(template=usc.context)
                except:
                    l = None
                if l is not None:
                    if scroll != usc.context and not (l.default and scroll == ''):
                        if scroll is not None:
                            newkey = key.replace(scroll, usc.context)
                        else:
                            newkey = key + '/_c' + usc.context
                        props = get_cache_or_render(request.user, newkey, False, forcerender=False, request=request)
                        renders = props['rendered_list']
                        paramdict = props['paramdict']
                        if len(renders) > 2:
                            #if theres only one, it's been pre-rendered and will be added later. Otherwise this function pre-renders for us
                            for val in renders:
                                if val['div'] == l.div_id:
                                    #re-render stuff only in the area of the page we are changing context within
                                    rendered_list.append(val)
            deferred.defer(get_cache_or_render, request.user, key, False, True, None)
            #Now render side effects
            sideeffects = SideEffectCache.objects.filter(user_salt_cache=usc)
            for s in sideeffects:
                if s.object_specific:
                    div = s.div_id + str(parent_pk)
                else:
                    div = s.div_id
                if usc.model_cache is not None:
                    deferred.defer(usc.model_cache.render, {'object': obj, 'user': obj.user}, True)

                rendered_list.append({'scroll_to': True, 'div': div, 'type': s.jquery_cmd, 'html':
                                    s.render(RequestContext(request, {'salted': True, 'object': obj, 'user': request.user}))})
            data['output'] = rendered_list
            data['FAIL'] = False
            if l is not None:
                deferred.defer(l.get_or_create_list, key, paramdict, forcerender=True)
        else:
            #if the side effect is null
            data['FAIL'] = True
        if 'application/json' in request.META.get('HTTP_ACCEPT', ''):
            return HttpResponse(simplejson.dumps(data),
                                mimetype='application/json')


def post_cache(user, key, div, request):

    if key is not None:
        rendertype, paramdict = interpret_hash(key)
        rendered_list = []
        u = UserSaltCache.objects.filter(div_id="#" + div)

        csrf_val = request.COOKIES.get('csrftoken', None)
        csrf_t = "<div style='display:none'><input type='hidden' value='" + str(csrf_val) + "' name='csrfmiddlewaretoken'></div>"
        for usc in u:
            #object might be specified in the POST data, if obj specific data in form
            obj_id = request.POST.get('object_pk', None)
            ctype_id = request.POST.get('content_type', None)
            #if the object is not specificed in POST or this UserSaltCache has no associated ModelCache
            if obj_id is None or not usc.object_specific:
                ctype_id = paramdict.get('TYPE_KEY', None)
                obj_id = paramdict.get('OBJ_KEY', None)
            #get the object related to USC
            obj = get_object_or_none(ctype_id, obj_id)
            div_id = usc.div_id
            #if no ModelCache, then there are multiple elements on one page and we need to specify via div_id
            if usc.object_specific:
                div_id += obj_id
            rendered_list.append({'obj_pk': obj_id, 'usc_pk': usc.pk, 'toggle': usc.is_toggle, 'div': div_id, 'type': usc.jquery_cmd, 'html':
                                    usc.render(RequestContext(request, {'dimension': paramdict.get('DIM_KEY', None), 'object': obj, 'user': user, 'csrf_string': csrf_t}))})

        return rendered_list, paramdict


def get_cache_or_render(user, key, empty, forcerender=False, request=None):
    """
    get_cache_or_render is required for objects to be returned by
    AJAX requests based on the type of object. This pre-renders
    content so that it is quickly accessible using the oa_cache
    models to interface with templates and div ids derived from css.
    This allows designers to still maintain what templates are pre-rendered
    to improve response time.

    Forms cannot be rendered in this way, as the POST request is sent
    to pirate_core.views.welcome_page, the root of www.openassembly.org/
    **instead we use JS method adObject in detail_dyn.html

    To overcome this only specified cached content in:
        src/pirate_core/templatetags/pp_url/TEMPLATE_DICT
    """
    #init
    tot_items = None
    if key is not None:
        rendertype, paramdict = interpret_hash(key)
    rendered_list = []

    #get the obj if it exists
    ctype_id = paramdict.get('TYPE_KEY', None)
    obj_id = paramdict.get('OBJ_KEY', None)
    dimension = paramdict.get('DIM_KEY', None)
    scroll_to = paramdict.get('SCROLL_KEY', None)
    obj = get_object_or_none(ctype_id, obj_id)

    if dimension is not None and not empty:
        render = False
    else:
        render = True

    #model specific code: if this is an item or user render that obj first
    try:
        m = ModelCache.objects.get(content_type=rendertype, main=True)
        u = UserSaltCache.objects.filter(model_cache=m)
    except:
        m = None
        u = []
    if (rendertype == 'item' or rendertype == 'user' or rendertype == 'arpv') and render:
        if rendertype == 'user':
            forcerender = True
    if m is not None:
        contextual = {'user': user,
                    'dimension': paramdict.get('DIM_KEY', None),
                    'start': paramdict.get('START_KEY', 0), 'end': paramdict.get('END_KEY', 20)}
        #if theres no obj, specify the user as the main object
        if obj is not None:
            contextual['object'] = obj
        else:
            contextual['object'] = user

        rendered_list.append({'div': m.div_id,
            'type': 'html', 'html': m.render(contextual,
                    forcerender=True)})

    if request is not None:
        csrf_val = request.COOKIES.get('csrftoken', None)
        csrf_t = "<div style='display:none'><input type='hidden' value='" + str(csrf_val) + "' name='csrfmiddlewaretoken'></div>"
    else:
        csrf_t = ''

    #list specific code:loads after model so detailed content is loaded first
    #if we aren't renderind the main content, we only want to render the list associated with scrolling
    ###Warning: this breaks if you try to display two lists on one page
    if dimension:
        lists = ListCache.objects.filter(content_type=rendertype, template=dimension)
    if not dimension or lists.count() == 0:
        lists = ListCache.objects.filter(content_type=rendertype, default=True)
    for l in lists:
        rendered_list.append({'div': l.div_id, 'html': '', 'type': 'html'})
        m = l.model_cache
        #if we aren't forcerendering, try to get rendered_list from memcache
        renders = None
        if not forcerender:
            #renders[0] -> rendered_list | renders[1] -> cached_list | renders[2] -> tot_items
            renders = memcache.get(key + str(l.pk))
        if renders is None or forcerender:
            renders = []
            #get list of objects to be rendered
            cached_list, tot_items = l.get_or_create_list(key, paramdict, forcerender=forcerender)
            for li in cached_list:
                #render each object in the list
                html = m.render({'object': li}, forcerender=forcerender)
                renders.append({'div': m.div_id, 'html': html, 'type': m.jquery_cmd})

            memcache.set(str(key) + str(l.pk), (renders, cached_list, tot_items))
        else:
            renders, cached_list, tot_items = renders
        rendered_list.extend(renders)

        #Get Dybamic inputs not linked to a user
        lu = UserSaltCache.objects.filter(model_cache=m, object_specific=True, is_toggle=False, opposite=False)
        for usc in lu:
            rendered_list.append({'div': usc.div_id, 'type': usc.jquery_cmd, 'html':
                                usc.render(RequestContext(request, {'dimension': paramdict.get('DIM_KEY', None),
                                'object': obj}))})

        #add usersaltcache if there is request data
        if request is not None:
            for li in cached_list:
                #user requested this, not auto-update. generate user specific html
                lu = UserSaltCache.objects.all().filter(model_cache=m, is_toggle=False)
                for usc in lu:
                    if not usc.is_recursive:
                        try:
                            rendered_list.append({'div': usc.div_id + str(li.pk), 'type': usc.jquery_cmd, 'html':
                            usc.render(RequestContext(request, {'dimension': paramdict.get('DIM_KEY', 'n'),
                            'object': li, 'user': user, 'csrf_string': csrf_t}))})
                        except:
                            raise ValueError(str(usc))
                    else:
                        #if it's recursive we need to also render all the children USCs
                        recursive_list = usc.render(RequestContext(request, {'dimension': paramdict.get('DIM_KEY', 'n'),
                                'object': li, 'user': user, 'csrf_string': csrf_t}))
                        for html, pk in recursive_list:
                            rendered_list.append({'div': usc.div_id + str(pk), 'type': usc.jquery_cmd, 'html': html})
        if tot_items is not None:
            lu = UserSaltCache.objects.filter(model_cache=m, template="rangelist.html")
            for usc in lu:
                rangelist = get_rangelist(paramdict.get('START_KEY', 0), paramdict.get('END_KEY', 20), tot_items)
                html = usc.render(RequestContext(request, {'rangelist': rangelist,
                        'start': paramdict.get('START_KEY', 0), 'end': paramdict.get('END_KEY', 20),
                        'dimension': paramdict.get('DIM_KEY', None), 'object': obj}))
                rendered_list.append({'div': usc.div_id, 'type': usc.jquery_cmd, 'html': html})

    #now add all the UserSaltCache objects from this page
    #THIS REQUIRES A REQUEST OBJECT FO' CSRF
    if request is not None:
        for usc in u:
            rendered_list.append({'div': usc.div_id, 'type': usc.jquery_cmd, 'html':
                        usc.render(RequestContext(request, {'dimension': paramdict.get('DIM_KEY', None),
                            'object': obj, 'user': user, 'csrf_string': csrf_t}))})
    if rendered_list == []:
        context = RequestContext(request, {'search': paramdict.get('SEARCH_KEY', ''),
                                'dimension': paramdict.get('DIM_KEY', None),
                                 'user': user, 'csrf_string': csrf_t})
        if obj is not None:
            context['object'] = obj
        val = render_to_string(rendertype + '.html', context)
        rendered_list = [{'div': '#content', 'html': val, 'type': 'html'}]

    #render all the user salt caches associated with this list
    #i.e. the Sort By: is a user salt cache
    lu = UserSaltCache.objects.filter(opposite=True).exclude(model_cache=m)
    for usc in lu:
            rendered_list.append({'div': usc.div_id, 'type': usc.jquery_cmd, 'html': ''})

    return {'rendered_list': rendered_list, 'paramdict': paramdict, 'render': render, 'scroll_to': scroll_to}


"""
Automatically updates ranked list of different views accessed,
to reduce latency

In the future this update will be dynamic
conditional on the activity of
that page view.#list/_s0/_e20/_dhn

"""


def commit_update(user_pk, key):
    prop = get_cache_or_render(user_pk, key, True, forcerender=True, request=None)


def update_ranks(request):
    codes = memcache.get("rank_update_codes")
    if codes is None:
        codes = {}
        memcache.add("rank_update_codes", codes, 60)
        goto = '/200.html'
        return HttpResponseRedirect(goto)
    else:
        for key, arg_dict in codes.items():
                deferred.defer(commit_update, None, key, _countdown=60)
        goto = '/200.html'
        return HttpResponseRedirect(goto)


def load_page(request):
    if request.method == 'GET':
        data = {'output': []}
        hashed = request.GET.get('hash')[1:]
        empty = request.GET.get('empty', None)
        if hashed == '' and empty != 'false':
            if request.user.is_authenticated():
                topic, is_new = Topic.objects.get_or_create(summary="Occupy The Internet")
                content_type = ContentType.objects.get_for_model(topic)
                hashed = 'list/_t' + str(content_type.pk) + '/_o' + str(topic.pk) + '/_s0/_e20/_dn'
            else:
                hashed = 'landing'
        if hashed != '':
            props = get_cache_or_render(request.user, hashed, empty, request=request)
            if props['render']:
                #if the c
                props['rendered_list'].insert(0, {'div': '#content', 'type': 'html', 'html': ''})
            for d in props['rendered_list']:
                data['output'].append(d)
            deferred.defer(create_view, request.user.username, request.META.get('REMOTE_ADDR'), props['paramdict'].get('OBJ_ID', None), _countdown=10)
            data['FAIL'] = False
            if 'SCROLL_KEY' in props['paramdict']:
                data['scroll_to'] = '#' + props['paramdict']['SCROLL_KEY']

        if 'application/json' in request.META.get('HTTP_ACCEPT', ''):
                return HttpResponse(simplejson.dumps(data),
                                    mimetype='application/json')
    elif request.method == 'POST':
        data = {'FAIL': False}
        #we just want to update the request changes and return what is rendered
        q = QueryDict('', mutable=True)
        p = request.POST.copy()
        hashed = p.pop('hash', '')[0][1:]
        for k, v in p.items():
            #cut off javascript 'form[...]'' in key to get ...
            q[k[5:-1]] = v
        q['hash'] = hashed
        request.POST = q
        div = request.POST['form_id']
        rendered_list, param_dict = post_cache(request.user, hashed, div, request)

        data['output'] = rendered_list
        #now we want to post to the cache and re-render the appropriate part of the page
        if 'application/json' in request.META.get('HTTP_ACCEPT', ''):
                return HttpResponse(simplejson.dumps(data),
                                    mimetype='application/json')


def load_page_ret(request, ts, url, c):
    response = c.get(url)
    return response


def nuke_memcache(request):
    if request.user.is_authenticated and request.user.is_staff:
        memcache.flush_all()
        goto = '/200.html'
        return HttpResponseRedirect(goto)
